/*
 * Matt Adams
 * TCSS 558 HW1
 * Fall 2013
 */
package client;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;

import tools.CheckArgument;
import tools.Log;

/**
 * SpellingClient handles common functionality between spelling clients.
 * 
 * @author Matt Adams
 */
public abstract class SpellingClient {
    
    protected final String host;
    protected final int port;
    protected final List<String> queryWords;
    protected final InetAddress address;
    
    /**
     * Constructs SpellingClient object.
     * 
     * @param hostPortWordArray Host, port, and query words array.
     */
    public SpellingClient(String[] hostPortWordArray) {
        if (hostPortWordArray.length < 3) {
            Log.err("Usage: " + super.getClass().getName() + " <hostname> <port> <word> [<word> ...]");
            errorClose();
        }       
        this.host = hostPortWordArray[0];
        this.port = CheckArgument.isValidPort(hostPortWordArray[1]);
        this.queryWords = createQueryWordList(hostPortWordArray);
        this.address = createInetAddress();
        Log.out("Client setup with Host: " + this.host + " and port: " + this.port);
    }
    
    /**
     * Queries a server for a specified word.
     * 
     * @param word Word sent to a server as a query.
     */
    public void queryServer(final String word) {
        send(word);
        receive(word);
    }
    
    /**
     * Displays the server response from a query as a log.
     * 
     * @param queriedWord Word queried.
     * @param numberOfWords Number of close words.
     * @param suggestedWords List of suggested words as string.
     */
    public void displayQueryResponse(final String queriedWord, final int numberOfWords, final String suggestedWords) {
        if (numberOfWords < 0) {
            Log.out(queriedWord + " is spelled correctly.");
        } else if (numberOfWords == 0) {
            Log.out(queriedWord + " is spelled incorrect1y. There are no suggestions.");
        } else {
            Log.out(queriedWord + " is spelled incorrect1y.");
            Log.out("There are " + numberOfWords + " suggested words: " + suggestedWords);
        }
    }
    
    /**
     * Properly displays server response to query.
     * 
     * @param queriedWord Queried word.
     * @param numberOfWords Number of close words.
     * @param closeWords Set of close words.
     * @return Returns formatted string of close words.
     */
    public String displayQueryResponse(final String queriedWord, final int numberOfWords, final SortedSet<String> closeWords) {
        String closeWordString = null;
        if (numberOfWords < 0) {
            Log.out(queriedWord + " is spelled correctly.");
        } else if (numberOfWords == 0) {
            Log.out(queriedWord + " is spelled incorrectly. There are no suggestions.");
        } else {     
            final StringBuilder sb = new StringBuilder();
            final Iterator<String> itr = closeWords.iterator();
            int count = 1;
            while (itr.hasNext() && count <= numberOfWords) {
                final String closeWord = itr.next();
                sb.append(closeWord);
                sb.append(" ");
                count++;
            }
            closeWordString = sb.toString();
            Log.out(queriedWord + " is spelled incorrectly.");
            Log.out("There are " + numberOfWords + " suggested words: " + closeWordString);
        }
        return closeWordString;
    }
    
    /**
     * Abstract method to receive data from a server.
     * 
     * @param word Word used in query.
     */
    protected abstract void receive(final String word);
    
    /**
     * Abstract method to send data to a server.
     * 
     * @param word Word used in query.
     */
    protected abstract void send(final String word);
    
    /**
     * Creates and returns a list of all words that will be queried to server.
     * 
     * @param hostPortWordArray String listing of the host, port and query words.
     * @return Returns list of words to query.
     */
    private List<String> createQueryWordList(String[] hostPortWordArray) {
        List<String> result = new ArrayList<String>();
        for (int i = 2; i < hostPortWordArray.length; i++) {
            result.add(hostPortWordArray[i]);
        }
        return result;
    }
    
    /**
     * Creates an InetAddress from a host name.
     * 
     * @return Returns an InetAddress.
     */
    private InetAddress createInetAddress() {
        InetAddress address = null;
        try {
            address = InetAddress.getByName(this.host);
        } catch (UnknownHostException e) {
            Log.err("Unknown host, failed to create InetAddress: " + e.getMessage());
            errorClose();
        }  
        return address;
    }
    
    /**
     * Closes connection due to error.
     * @param socket Socket to be closed due to error.
     */
    public void errorClose(DatagramSocket socket) {
        socket.close();
        errorClose();
    }
    
    /**
     * Closes connection due to error.
     * @param socket Socket to be closed due to error.
     */
    public void errorClose(Socket socket) {
        try {
            socket.close();
        } catch (IOException e) {
            Log.err("Failed to close socket: " + e);
        }
        errorClose();
    }
    
    /**
     * Closes connection due to error.
     */
    public void errorClose() {
        Log.out("Client shutdown from error.");
        System.exit(1);
    }
    
}
